
#include "../iface/LibSSH2Interface.h"
#include "../aprtools/APRFile.h"
#include "../aprtools/APRAutoLock.h"
#include "PersistentForwardChannel.h"

/** Gets an SSH channel which represents a tunnel across an open SSH
 * connection.
 * 
 * @param ilocalhost The local host to which to attach the local end
 * of the tunnel.
 * 
 * @param ilocalport The local port to which to attach the local end
 * of the tunnel.
 * 
 * @param iremotehost The remote host to point the remote end of the
 * tunnel at.
 * 
 * @param iremoteport The remote port to point the remote end of the
 * tunnel at.
 * 
 * @return The libssh2 channel pointer referring to the libssh2
 * channel. */
LIBSSH2_CHANNEL *PersistentForwardChannel::getPersistentForwardingChannel(const char *ilocalhost, int ilocalport, const char *iremotehost, int iremoteport)
{
   LIBSSH2_CHANNEL *itchannel = libssh2_channel_direct_tcpip_ex(const_cast<LIBSSH2_SESSION *>(tclient->getSSHSession()), iremotehost, iremoteport, ilocalhost, ilocalport);
   if (itchannel == NULL)
   {
      errprint("could not establish tunnel on port %d (ssh -L %d:%s:%d)", ilocalport, ilocalport, iremotehost, iremoteport);
   } else
      libssh2_session_set_blocking(const_cast<LIBSSH2_SESSION *>(tclient->getSSHSession()), 0);
   return itchannel;
}

/** The constructor for the persistent forwarding channel.
 * 
 * @param itclient The SSH client over which the persistent forwarding
 * channel is the be constructed.
 * 
 * @param inothreads The number of threads that start in the pool that
 * serves the connections. This is usually one for security reasons
 * when telescoping (one connection at a time).
 * 
 * @param ilocalportno The local port to which to attach the local end
 * of the tunnel.
 * 
 * @param ilocalhostname The local host to which to attach the local
 * end of the tunnel.
 * 
 * @param iremoteportno The remote port to point the remote end of the
 * tunnel at.
 * 
 * @param iremotehostname The remote host to point the remote end of
 * the tunnel at. */
PersistentForwardChannel::PersistentForwardChannel(SSHClient &itclient, int inothreads, int ilocalportno, const char *ilocalhostname,
   int iremoteportno, const char *iremotehostname) : SSHChannel(itclient),tchannel(NULL),fwdserver(NULL)
{
   localport = ilocalportno;
   localhostname = (ilocalhostname) ? strdup(ilocalhostname) : NULL;
   remoteport = iremoteportno;
   remotehostname = (iremotehostname) ? strdup(iremotehostname) : NULL;
   if (localhostname == NULL) { errprint("Local host name is NULL"); return; }
   if (remotehostname == NULL) { errprint("Remote host name is NULL"); return; }
   fwdserver = new ForwardServer(inothreads, ilocalportno, ilocalhostname, itclient.getMutex(), imp);//itclient.getMemoryPool());
   fwdserver->startSessionThreads();
   fwdserver->startListeningThread();
   int minitries = 50;
   int timeout = 1;
   for (int im = minitries; im--; )
   {
      if (fwdserver->isListening()) break;
      timeout <<= 1;
      timeout = (timeout > SSH_TIMEOUT_EAGAIN) ? SSH_TIMEOUT_EAGAIN : timeout;
      apr_sleep(timeout);
   }
   if (fwdserver->isListening() == 0)
   {
      errprint("persistent forwarding failed");
      delete fwdserver;
      fwdserver = NULL;
   } else
   {
      tchannel = getPersistentForwardingChannel(localhostname, localport, remotehostname, remoteport);
      if (fwdserver)
         fwdserver->setForwardingChannel(tchannel);
   }
}

/** The constructor for the persistent forwarding channel.  This one
 * slightly more usefully takes a range of local ports, randomly
 * selecting one on each bind failure to enable a more robust
 * system. The finalised port is then queried by getLocalListenPort().
 * 
 * @param itclient The SSH client over which the persistent forwarding
 * channel is the be constructed.
 * 
 * @param inothreads The number of threads that start in the pool that
 * serves the connections. This is usually one for security reasons
 * when telescoping (one connection at a time).
 * 
 * @param ilocalportstart The start of the port range to try to attach
 * the local end of the tunnel to.
 * 
 * @param ilocalportend The end of the port range to try to attach
 * the local end of the tunnel to.
 * 
 * @param ilocalhostname The local host to which to attach the local
 * end of the tunnel.
 * 
 * @param iremoteport The remote port to point the remote end of the
 * tunnel at.
 * 
 * @param iremotehostname The remote host to point the remote end of
 * the tunnel at. */
PersistentForwardChannel::PersistentForwardChannel(SSHClient &itclient, int inothreads, int ilocalportstart, int ilocalportend,
   const char *ilocalhostname, int iremoteport, const char *iremotehostname) : SSHChannel(itclient),tchannel(NULL),fwdserver(NULL)
{
   localport = 0;
   localhostname = (ilocalhostname) ? strdup(ilocalhostname) : NULL;
   remoteport = iremoteport;
   remotehostname = (iremotehostname) ? strdup(iremotehostname) : NULL;
   if (localhostname == NULL) { errprint("Local host name is NULL"); return; }
   if (remotehostname == NULL) { errprint("Remote host name is NULL"); return; }
   int maxtries = 10;
   for (int i = maxtries; i--; )
   {
      int newport = (rand() % (ilocalportend - ilocalportstart)) + ilocalportstart;
      localport = newport;
      fwdserver = new ForwardServer(inothreads, newport, ilocalhostname, itclient.getMutex(), imp);//itclient.getMemoryPool());
      fwdserver->startSessionThreads();
      fwdserver->startListeningThread();
      int minitries = 50;
      int timeout = 1;
      for (int im = minitries; im--; )
      {
         if (fwdserver->isListening()) break;
         timeout <<= 1;
         timeout = (timeout > SSH_TIMEOUT_EAGAIN) ? SSH_TIMEOUT_EAGAIN : timeout;
         apr_sleep(timeout);
      }
      if (fwdserver->isListening() == 0)
      {
         errprint("%d tries remaining...", i);
         delete fwdserver;
         fwdserver = NULL;
      } else
      {
         tchannel = getPersistentForwardingChannel(localhostname, localport, remotehostname, remoteport);
         if ((tchannel) && (fwdserver))
         {
            fwdserver->setForwardingChannel(tchannel);
            break;
         }
      }
   }
}

/** Destructor for the persistent forwarding channel. The foward
 * server must be destroyed first to prevent errors incurred by a
 * non-existant virtual channel. */
PersistentForwardChannel::~PersistentForwardChannel()
{
   if (fwdserver) delete fwdserver;
   fwdserver = NULL;
   if (tchannel) stopChannel((SSHChannel *)this, tchannel);
   tchannel = NULL;
   if (localhostname) free(localhostname);
   localhostname = NULL;
   if (remotehostname) free(remotehostname);
   remotehostname = NULL;
}
